<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>RTF to HTML Converter</title>
  <style>
* {
  box-sizing: border-box;
}

body {
  font-family: Helvetica, Arial, sans-serif;
  max-width: 1400px;
  margin: 0 auto;
  padding: 20px;
  background: #f5f5f5;
}

h1 {
  color: #333;
  margin-bottom: 10px;
}

.instructions {
  background: #e3f2fd;
  border-left: 4px solid #2196f3;
  padding: 15px;
  margin-bottom: 20px;
  border-radius: 4px;
}

.paste-area {
  width: 100%;
  min-height: 120px;
  padding: 15px;
  border: 2px dashed #ccc;
  border-radius: 8px;
  background: white;
  font-size: 16px;
  margin-bottom: 20px;
  outline: none;
  transition: border-color 0.3s;
}

.paste-area:focus {
  border-color: #2196f3;
  border-style: solid;
}

.results {
  display: grid;
  gap: 20px;
}

.section {
  background: white;
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.section h2 {
  margin-top: 0;
  color: #2196f3;
  font-size: 18px;
  border-bottom: 2px solid #e0e0e0;
  padding-bottom: 10px;
  margin-bottom: 15px;
}

.preview-container {
  background: #f9f9f9;
  border: 1px solid #e0e0e0;
  border-radius: 4px;
  padding: 0;
  overflow-x: auto;
  overflow-y: auto;
  margin-bottom: 15px;
  max-width: 100%;
}

.preview-container pre {
  margin: 0;
  padding: 15px;
  white-space: pre;
  overflow-x: visible;
  width: max-content;
  min-width: 100%;
}

.code-container {
  position: relative;
  max-width: 100%;
}

.code-output {
  width: 100%;
  min-height: 200px;
  padding: 15px;
  border: 1px solid #e0e0e0;
  border-radius: 4px;
  background: #f9f9f9;
  font-family: 'Courier New', monospace;
  font-size: 16px;
  resize: vertical;
  overflow-x: auto;
  white-space: pre;
}

.copy-btn {
  display: block;
  margin-bottom: 10px;
  padding: 8px 16px;
  background: #2196f3;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  font-family: Helvetica, Arial, sans-serif;
  width: fit-content;
}

.copy-btn:hover {
  background: #1976d2;
}

.copy-btn:active {
  background: #0d47a1;
}

.copy-btn.copied {
  background: #4caf50;
}

.empty-state {
  text-align: center;
  color: #999;
  padding: 40px;
  font-size: 18px;
}

.error {
  background: #ffebee;
  border-left: 4px solid #f44336;
  padding: 15px;
  margin-bottom: 20px;
  border-radius: 4px;
  color: #c62828;
}
  </style>
</head>
<body>
  <h1>RTF to HTML converter</h1>
  
  <div class="instructions">
    <strong>Instructions:</strong> Paste your RTF content below. If RTF format is detected, it will be converted to colorized HTML with inline styles.
  </div>

  <textarea class="paste-area" placeholder="Click here and paste your RTF content (Cmd+V or Ctrl+V)..."></textarea>

  <div class="results" id="results">
    <div class="empty-state">Paste RTF content to see the converted HTML</div>
  </div>

<script type="module">
const pasteArea = document.querySelector('.paste-area');
const resultsDiv = document.getElementById('results');

function parseRTFColor(colorDef) {
  const redMatch = colorDef.match(/\\red(\d+)/);
  const greenMatch = colorDef.match(/\\green(\d+)/);
  const blueMatch = colorDef.match(/\\blue(\d+)/);
  
  if (redMatch && greenMatch && blueMatch) {
    const r = parseInt(redMatch[1]);
    const g = parseInt(greenMatch[1]);
    const b = parseInt(blueMatch[1]);
    return `rgb(${r}, ${g}, ${b})`;
  }
  return null;
}

function parseRTFColorTable(rtf) {
  const colorTableMatch = rtf.match(/\{\\colortbl;([^}]+)\}/);
  if (!colorTableMatch) return [];
  
  const colorTable = [null]; // Index 0 is the auto/default color (represented by leading ;)
  const colorDefs = colorTableMatch[1].split(';');
  
  for (const colorDef of colorDefs) {
    if (colorDef.trim()) { // Skip empty entries
      const color = parseRTFColor(colorDef.trim());
      colorTable.push(color);
    }
  }
  
  return colorTable;
}

function rtfToHtml(rtf) {
  const colorTable = parseRTFColorTable(rtf);
  
  // Extract the background color and default text color from the initial formatting
  let backgroundColor = null;
  let defaultTextColor = null;
  
  // Look for the initial color settings: \cf2 \cb3
  const initialFormatMatch = rtf.match(/\\f0\\fs\d+\s+\\cf(\d+)\s+\\cb(\d+)/);
  if (initialFormatMatch) {
    const textColorIndex = parseInt(initialFormatMatch[1]);
    const bgColorIndex = parseInt(initialFormatMatch[2]);
    defaultTextColor = colorTable[textColorIndex];
    backgroundColor = colorTable[bgColorIndex];
  }
  
  // Skip all header sections by finding where the actual paragraph content starts
  // Look for \pard which marks the start of paragraph content
  const pardIndex = rtf.indexOf('\\pard');
  if (pardIndex === -1) {
    throw new Error('Could not find paragraph content');
  }
  
  // Find the pattern that marks the start of actual text content
  // Format is typically: \f0\fs20 \cf2 \cb3 \CocoaLigature0 [space] [actual content]
  const contentPattern = /\\f0\\fs\d+\s+\\cf\d+\s+\\cb\d+\s+\\CocoaLigature\d+\s+/;
  const match = rtf.substring(pardIndex).match(contentPattern);
  
  if (!match) {
    throw new Error('Could not find content start marker');
  }
  
  const contentStartOffset = pardIndex + match.index + match[0].length;
  
  // Find the last closing brace
  const lastBrace = rtf.lastIndexOf('}');
  
  let content = rtf.substring(contentStartOffset, lastBrace);
  
  let html = '';
  let i = 0;
  let currentColor = defaultTextColor; // Start with the default text color
  let currentBgColor = backgroundColor; // Start with the default background color
  let isBold = false;
  
  while (i < content.length) {
    const char = content[i];
    
    if (char === '\\') {
      // Look ahead to see what control word this is
      let controlWord = '';
      let j = i + 1;
      
      // Check for hex escape \'XX
      if (j < content.length && content[j] === "'") {
        j++; // skip the '
        let hexCode = '';
        while (j < content.length && /[0-9a-fA-F]/.test(content[j]) && hexCode.length < 2) {
          hexCode += content[j];
          j++;
        }
        if (hexCode.length === 2) {
          const charCode = parseInt(hexCode, 16);
          html += String.fromCharCode(charCode);
          i = j;
          continue;
        }
      }
      
      // Collect the control word
      while (j < content.length && /[a-z]/i.test(content[j])) {
        controlWord += content[j];
        j++;
      }
      
      // Get any numeric parameter
      let numParam = '';
      while (j < content.length && /[0-9]/.test(content[j])) {
        numParam += content[j];
        j++;
      }
      
      // Skip the space after control word if present
      if (j < content.length && content[j] === ' ') {
        j++;
      }
      
      if (controlWord === 'cf') {
        // Color change
        const colorIndex = parseInt(numParam);
        currentColor = colorTable[colorIndex] || defaultTextColor;
        i = j;
      } else if (controlWord === 'cb') {
        // Background color change
        const colorIndex = parseInt(numParam);
        currentBgColor = colorTable[colorIndex] || backgroundColor;
        i = j;
      } else if (controlWord === 'b') {
        // Bold formatting
        if (numParam === '0') {
          isBold = false;
        } else {
          isBold = true;
        }
        i = j;
      } else if (controlWord === 'uc') {
        // Unicode character count - skip
        i = j;
      } else if (controlWord === 'u') {
        // Unicode character
        const codePoint = parseInt(numParam);
        if (codePoint < 0) {
          // Negative values need to be converted (two's complement for 16-bit)
          const unsigned = 65536 + codePoint;
          html += String.fromCharCode(unsigned);
        } else {
          html += String.fromCharCode(codePoint);
        }
        // Skip the alternative representation (usually follows \u)
        // Look for a space and then skip the next character
        if (j < content.length && content[j] === '?') {
          j++;
        }
        i = j;
      } else if (controlWord === '') {
        // Check for escaped special character or newline
        if (i + 1 < content.length) {
          const nextChar = content[i + 1];
          if (nextChar === '\\') {
            html += '\\';
            i += 2;
          } else if (nextChar === '\n') {
            html += '\n';
            i += 2;
          } else {
            // Just a backslash followed by something else
            i++;
          }
        } else {
          i++;
        }
      } else {
        // Unknown control word, skip it
        i = j;
      }
    } else if (char === '\n') {
      // Preserve line breaks
      html += '\n';
      i++;
    } else {
      // Regular character - collect text until we hit a control code or newline
      let text = '';
      let startPos = i;
      
      while (i < content.length && content[i] !== '\\' && content[i] !== '\n') {
        text += content[i];
        i++;
      }
      
      // Don't trim - preserve spaces
      if (text.length > 0) {
        const styles = [];
        if (currentColor) {
          styles.push(`color: ${currentColor}`);
        }
        if (currentBgColor && currentBgColor !== backgroundColor) {
          styles.push(`background: ${currentBgColor}`);
        }
        if (isBold) {
          styles.push('font-weight: bold');
        }
        
        if (styles.length > 0) {
          html += `<span style="${styles.join('; ')};">${escapeHtml(text)}</span>`;
        } else {
          html += escapeHtml(text);
        }
      }
    }
  }
  
  return { html, backgroundColor, defaultTextColor };
}

function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

pasteArea.addEventListener('paste', async (e) => {
  e.preventDefault();
  
  const clipboardData = e.clipboardData;
  resultsDiv.innerHTML = '';
  
  if (!clipboardData) {
    resultsDiv.innerHTML = '<div class="empty-state">No clipboard data detected</div>';
    return;
  }
  
  // Look for RTF data
  const rtfData = clipboardData.getData('text/rtf');
  
  if (!rtfData) {
    resultsDiv.innerHTML = '<div class="error">No RTF format detected in clipboard. Please copy RTF content.</div>';
    return;
  }
  
  try {
    const result = rtfToHtml(rtfData);
    const htmlOutput = result.html;
    const backgroundColor = result.backgroundColor;
    const defaultTextColor = result.defaultTextColor;
    
    // Build the full HTML with background and text color if available
    let preStyle = '';
    const styles = [];
    if (backgroundColor) {
      styles.push(`background: ${backgroundColor}`);
    }
    if (defaultTextColor) {
      styles.push(`color: ${defaultTextColor}`);
    }
    if (styles.length > 0) {
      preStyle = ` style="${styles.join('; ')}; padding: 15px; border-radius: 4px;"`;
    }
    const fullHtml = `<pre${preStyle}>${htmlOutput}</pre>`;
    
    // Create raw RTF section
    const rawSection = document.createElement('div');
    rawSection.className = 'section';
    rawSection.innerHTML = `
      <h2>Raw RTF data</h2>
      <div class="code-container">
        <button class="copy-btn">Copy</button>
        <textarea class="code-output" readonly>${escapeHtml(rtfData)}</textarea>
      </div>
    `;
    resultsDiv.appendChild(rawSection);
    
    // Add copy functionality for raw RTF
    const rawCopyBtn = rawSection.querySelector('.copy-btn');
    const rawTextarea = rawSection.querySelector('.code-output');
    
    rawCopyBtn.addEventListener('click', () => {
      rawTextarea.select();
      document.execCommand('copy');
      rawCopyBtn.textContent = 'Copied!';
      rawCopyBtn.classList.add('copied');
      setTimeout(() => {
        rawCopyBtn.textContent = 'Copy';
        rawCopyBtn.classList.remove('copied');
      }, 2000);
    });
    
    // Create HTML code section
    const codeSection = document.createElement('div');
    codeSection.className = 'section';
    codeSection.innerHTML = `
      <h2>HTML code</h2>
      <div class="code-container">
        <button class="copy-btn">Copy</button>
        <textarea class="code-output" readonly>${escapeHtml(fullHtml)}</textarea>
      </div>
    `;
    resultsDiv.appendChild(codeSection);
    
    // Add copy functionality
    const copyBtn = codeSection.querySelector('.copy-btn');
    const textarea = codeSection.querySelector('.code-output');
    
    copyBtn.addEventListener('click', () => {
      textarea.select();
      document.execCommand('copy');
      copyBtn.textContent = 'Copied!';
      copyBtn.classList.add('copied');
      setTimeout(() => {
        copyBtn.textContent = 'Copy';
        copyBtn.classList.remove('copied');
      }, 2000);
    });
    
    // Create preview section
    const previewSection = document.createElement('div');
    previewSection.className = 'section';
    previewSection.innerHTML = `
      <h2>Preview</h2>
      <div class="preview-container">
        ${fullHtml}
      </div>
    `;
    resultsDiv.appendChild(previewSection);
    
  } catch (error) {
    resultsDiv.innerHTML = `<div class="error">Error parsing RTF: ${error.message}</div>`;
  }
});

// Clear the textarea on paste
pasteArea.addEventListener('paste', () => {
  setTimeout(() => {
    pasteArea.value = '';
  }, 0);
});
</script>
</body>
</html>
